#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <strings.h>

#include <assert.h>

#include "common.h"
#include "memory.h"

int test_phys_memory() {
	phys_error	err;
	phys_mem	mem;
	
	uint8_t		byte;
	uint16_t	hword;
	uint32_t	word;
	uint32_t	idx;

#ifdef MIPS64
	uint64_t	dword;
	ssize_t		addrs[] = {	0x0000000000000000LL, /* 0  Boundry Condition */
							0xFFFFFFFFFFFFFFFFLL, /* 1  Boundry Condition */
							0x0040000000000000LL, /* 2  Aligned, new directory (1) */
							0x0040000000000002LL, /* 3  2 bytes off */
							0x0040000000000001LL, /* 4  1 byte off */

							0x004004000000000ELL, /* 5  Aligned, new directory (2) */
							0x00400400000000FFLL, /* 6  Unaligned, stays on page */
							0x004004000000FFFFLL, /* 7  Unaligned, spans boundry */

							0x004004004000FFF8LL, /* 8  Aligned, new directory (3) */
							0x004004004000000DLL, /* 9  Unaligned, stays on page */
							0x004004004000FFFDLL, /* 10 Unaligned, spans boundry */

							0x0080080080080000LL, /* 11 Aligned, new table */
							0x008008008008FFF4LL, /* 12 Unaligned, stays on page */
							0x008008008008FFFCLL, /* 13 Unaligned, spans page boundry */
							0x0080080080100004LL, /* 14 Unaligned, next page */

							0xAAABBBCCCDDD0000LL, /* 15 Long test */
						};
	mem = valloc(PAGE_SIZE);
	bzero(mem, PAGE_SIZE);
#else
	size_t		addrs[] = {	0x00000000L,	/* 0  Boundry Condition */
							0xFFFFFFFFL,	/* 1  Boundry Condition */
							0x00400000L,	/* 2  Aligned, new directory (1) */
							0x00400002L,	/* 3  2 bytes off */
							0x00400001L,	/* 4  1 byte off */

							0x0400400EL,	/* 5  Aligned, new directory (2) */
							0x040040FFL,	/* 6  Unaligned, stays on page */
							0x04004FFFL,	/* 7  Unaligned, spans boundry */

							0x0400FFF8L,	/* 8  Aligned, new directory (3) */
							0x0400F00DL,	/* 9  Unaligned, stays on page */
							0x0400FFFDL,	/* 10 Unaligned, spans boundry */

							0x0, 			/* 11 Unused */
							0x0,			/* 12 Unused */
							0x0,			/* 13 Unused */
							0x0,			/* 14 Unused */

							0xABCDD000L,	/* 15 Long test */

	mem = valloc(PAGE_SIZE);
	bzero(mem, PAGE_SIZE);
#endif

#define MEM_TEST(a, s, l, v)							\
	{	err = read_phys_memory(mem, (a), (s), &(l));	\
		assert(err == E_OKAY);							\
		assert((l) == 0);								\
		(l) = (v);										\
		err = write_phys_memory(mem, (a), (s), &(l));	\
		assert(err == E_OKAY);							\
		assert((l) == (v));								\
		(l) = 0;										\
		err = read_phys_memory(mem, (a), (s), &(l));	\
		assert(err == E_OKAY);							\
		assert((l) == (v));								\
	}
#define SPOT_TEST(a, s, l, v)							\
	{	(l) = 0;										\
		err = read_phys_memory(mem, (a), (s), &(l));	\
		assert(err == E_OKAY);							\
		assert((l) == (v));								\
	}

	printf("\tTesting lower boundry...\n");
	MEM_TEST(addrs[0], 0x1, byte, 0xAA);
	printf("\tTesting upper boundry...\n");
	MEM_TEST(addrs[1], 0x1, byte, 0xBB);
	printf("Boundry conditions okay.\n");
	
	printf("\tTesting 8-bit operations...\n");
	MEM_TEST(addrs[2], 0x1, byte, 0x55);
	MEM_TEST(addrs[3], 0x1, byte, 0xAA);
	MEM_TEST(addrs[4], 0x1, byte, 0x99);
	printf("Byte operations okay.\n");

	printf("\tTesting 16-bit operations...\n");
	MEM_TEST(addrs[5], 0x2, hword, 0xEEEE);
	MEM_TEST(addrs[6], 0x2, hword, 0xBBBB);
	MEM_TEST(addrs[7], 0x2, hword, 0xFFFF);
	printf("Half word operations okay.\n");

	printf("\tTesting 32-bit operations...\n");
	MEM_TEST(addrs[8], 0x4, word, 0xAAAABBBB);
	MEM_TEST(addrs[9], 0x4, word, 0xBBBBAAAA);
	MEM_TEST(addrs[10], 0x4, word, 0xFEFEFEFE);
	printf("Word operations okay.\n");

#ifdef MIPS64
	printf("\tTesting 64-bit operations...\n");
	MEM_TEST(addrs[11], 0x8, dword, 0xFEFEFEFEFEFEFEFELL);
	MEM_TEST(addrs[12], 0x8, dword, 0xAABBCCDDEEFFEEDDLL);
	MEM_TEST(addrs[13], 0x8, dword, 0x5555555555555555LL);
	MEM_TEST(addrs[14], 0x8, dword, 0xCCCCCCCCCCCCCCCCLL);
	printf("Double word operations okay.\n");
#endif /* MIPS64 */
	
	printf("\tTesting region operations...\n");
	for(idx=0; idx<32*MEM_PAGE; idx++) {
		MEM_TEST(addrs[15]+idx, 0x1, byte, idx & 0xFF);
	}
	printf("\tTesting region integrity...\n");
	for(idx=0; idx<32*MEM_PAGE; idx+=4) {
		uint8_t check[4] = { idx & 0xFF, (idx+1) & 0xFF, (idx+2) & 0xFF, (idx+3) & 0xFF };
		SPOT_TEST(addrs[15]+idx, 0x4, word, *(uint32_t *)check);
	}
	printf("Regions okay.\n");

#undef SPOT_TEST
#undef MEM_TEST

	free_phys_memory(mem);
	
	return 0;
}

int main(int argc, char *argv[]) {
	printf("SPAM v0.01a Physical Memory Emulation\n");
#ifdef MIPS64
	printf("Running in 64-bit mode\n");
#else
	printf("Running in 32-bit mode\n");
#endif

	printf("Testing memory emulation module...\n");
	test_phys_memory();
	printf("Physical memory emulation okay.\n");

	return 0;
}

